// WebFrame.h
// 2008/12/18

#pragma once

/* */
#include "helper/UWebControl.h"
#include "helper/UStdStream.h"

/* */

// WebEntry
struct WebEntry {

	CStringW Uri;

	CStringW Path;

	CStringW MimeType;

	int ImageWidth;
	int ImageHeight;

	HWND Target;
	UINT Message;

	INT_PTR Cookie;

	int PageWidth;
	int PageHeight;

	DWORD ShotDelay;
	DWORD NavigateTimeout;

	bool Show;

	WebEntry()
	{
		ImageWidth  = 0;
		ImageHeight = 0;

		Target  = 0;
		Message = 0;

		Cookie = 0;

		PageWidth  = 0;
		PageHeight = 0;

		ShotDelay       = 0;
		NavigateTimeout = 0;

		Show = false;
	}

}; // WebEntry

const UINT WM_ADD_WEBENTRY  = WM_APP + 1000;
const UINT WM_POST_WEBENTRY = WM_APP + 1001;

/* */

// WebFrame
class WebFrame :
	public WTL::CFrameWindowImpl<WebFrame> {

	typedef WTL::CFrameWindowImpl<WebFrame> TBase;

	LuoUtils::UWebControl m_WebControl;

	enum {
		ID_WEB_CTRL = 1000
	};

	typedef ATL::CAtlList<WebEntry> Entries;

	Entries m_Items;

	WebEntry m_Item;

	enum {
		TICK_TIMEOUT  = 100,
		SWAIT_TIMEOUT = 900,

		TID_ENTRY      = 1,
		TID_NAVIGATING,
		TID_SNAP,
		TID_WAIT
	};

	DWORD m_dwStartTime;

	ULONG_PTR token_;

public:

	DECLARE_FRAME_WND_CLASS(_T("iASnapShotWebFrameClass"), 0)

	BEGIN_MSG_MAP_EX(WebFrame)
		MSG_WM_CREATE (OnCreate )
		MSG_WM_DESTROY(OnDestroy)

		MSG_WM_SIZE(OnSize)

		MSG_WM_TIMER(OnTimer)

		MESSAGE_HANDLER(WM_ADD_WEBENTRY,  OnAddWebEntry)
		MESSAGE_HANDLER(WM_POST_WEBENTRY, OnPostWebEntry)

		REFLECT_NOTIFICATIONS()

		CHAIN_MSG_MAP(TBase)
	END_MSG_MAP()

public:

	WebFrame() : m_dwStartTime(0),
		token_(0)
	{
		// Init GDI++
		Gdiplus::GdiplusStartupInput input;
		Gdiplus::GdiplusStartupOutput output = { 0, 0};
		Gdiplus::Status ret = Gdiplus::GdiplusStartup(&token_, &input, &output);
		if (ret != Gdiplus::Ok) {
			::MessageBox(NULL, _T("Fail to init GDI++!"), _T("Error"), MB_OK);
		}
	}

	~WebFrame()
	{
		// Shut down GDI++
		Gdiplus::GdiplusShutdown(token_);
	}

	static HWND CreateFrame()
	{
		WebFrame* frame = new WebFrame();

		HWND hwnd = frame->Create(
			0,
			0,
			L"LuoSnapShot",
			WS_POPUP | WS_MINIMIZEBOX,
			0);
		if (hwnd == 0) {
			delete frame;
			return 0;
		}

		return hwnd;
	}

	virtual void OnFinalMessage(
		HWND /*hWnd*/)
	{
		delete this;
	}

	/* */

private:

	/* */

	LRESULT OnCreate(LPCREATESTRUCT pCreateStruct)
	{
		InitWebControl();

		SetWindowPos(
			0,
			  0,   0,
			320, 240,
			SWP_NOZORDER);

		SetTimer(TID_ENTRY, TICK_TIMEOUT, 0);

		return 0;
	}

	void OnDestroy()
	{
	}

	/* */

	void InitWebControl()
	{
		HRESULT hRslt = m_WebControl.Create(
			*this,
			ID_WEB_CTRL);
		if (FAILED(hRslt)) {
			return;
		}

		m_WebControl.SetupAmbientDLControl(DLCTL_NO_SCRIPTS | DLCTL_NO_JAVA | DLCTL_NO_RUNACTIVEXCTLS | DLCTL_NO_DLACTIVEXCTLS | DLCTL_DLIMAGES);

		ATL::CWindow& ctrl = m_WebControl.GetControl();
		ctrl.SetWindowPos(
			0,
			0, 0, 0, 0,
			SWP_NOZORDER);
	}

	/* */

	void OnSize(UINT nType, WTL::CSize size)
	{
		if (nType != SIZE_MINIMIZED) {
			const int SPING = 2;

			int x  = SPING;
			int y  = SPING;
			int cx = size.cx - SPING * 2;
			int cy = size.cy - SPING * 2;

			ATL::CWindow& ctrl = m_WebControl.GetControl();
			ctrl.SetWindowPos(
				0,
				x, y,
				cx, cy,
				SWP_NOZORDER);
		}
	}

	/* */

	void Navigate(
		LPCWSTR uri,
		LPCWSTR entityBody)
	{
		ATL::CComVariant vtUri(uri);
		ATL::CComVariant vtNull;
		ATL::CComVariant vtPostData;
		ATL::CComVariant vtHeaders;

		if (entityBody != 0) {
			CStringA abody = LuoUtils::UTF8::ToBytes(entityBody);
			if (!abody.IsEmpty()) {
				{
					ATL::CComVariant var;

					SAFEARRAY* pArray = SafeArrayCreateVector(
						VT_UI1,
						0,
						abody.GetLength());
					if (pArray != 0) {
						SafeArrayLock(pArray);

						memcpy(
							pArray->pvData,
							abody,
							abody.GetLength());

						SafeArrayUnlock(pArray);

						var.vt     = VT_ARRAY | VT_UI1;
						var.parray = pArray;
					}

					vtPostData.Attach(&var);
				}

				{
					ATL::CComVariant var(L"Content-Type: application/x-www-form-urlencoded\r\n");

					vtHeaders.Attach(&var);
				}
			}
		}

		IWebBrowser2* web = m_WebControl.GetWebBrowser();
		web->Navigate2(
			&vtUri,
			&vtNull,
			&vtNull,
			&vtPostData,
			&vtHeaders);

		m_dwStartTime = timeGetTime();
	}

	/* */

	void OnTimer(UINT_PTR nIDEvent)
	{
		switch (nIDEvent) {
		case TID_ENTRY:
			DoEntry();
			break;

		case TID_NAVIGATING:
			DoNavigating();
			break;

		case TID_SNAP:
			DoSnap();
			break;

		case TID_WAIT:
			// ATLTRACE("HIDE\n");
			ShowWindow(SW_HIDE);

			KillTimer(TID_WAIT);
			SetTimer(TID_ENTRY, TICK_TIMEOUT, 0);
			Navigate(
				L"about:blank",
				0);

			break;
		}
	}

	/* */

	void DoEntry()
	{
		if (m_Items.IsEmpty()) {
			return;
		}

		m_Item = m_Items.RemoveTail();

		SetBrowserSize(m_Item.PageWidth, m_Item.PageHeight);

		Navigate(
			m_Item.Uri,
			0);

		KillTimer(TID_ENTRY);
		SetTimer(TID_NAVIGATING, TICK_TIMEOUT, 0);
	}

	void DoNavigating()
	{
		DWORD now     = timeGetTime();
		DWORD elapsed = now - m_dwStartTime;

		IWebBrowser2*   web = m_WebControl.GetWebBrowser();
		IHTMLDocument2* doc = m_WebControl.GetWebDocument();

		READYSTATE state = READYSTATE_UNINITIALIZED;
		web->get_ReadyState(&state);

		if (state == READYSTATE_COMPLETE && doc != 0) {
			if (m_Item.Show) {
				ShowWindow(SW_NORMAL);
			}

			KillTimer(TID_NAVIGATING);
			SetTimer(TID_SNAP, m_Item.ShotDelay, 0);
			return;
		}

		if (elapsed > m_Item.NavigateTimeout) {
			DoCancelTimeout();

			KillTimer(TID_NAVIGATING);
			SetTimer(TID_ENTRY, TICK_TIMEOUT, 0);
			return;
		}
	}

	void DoSnap()
	{
		if (m_Item.Show) {
			extern HINSTANCE g_hDLL;
			PlaySoundW(
				L"F4S",
				g_hDLL,
				SND_RESOURCE | SND_ASYNC);
		}

		DoCapture();

		KillTimer(TID_SNAP);

		if (!m_Item.Show) {
			if (IsWindowVisible()) {
				ShowWindow(SW_HIDE);
			}
			SetTimer(TID_ENTRY, TICK_TIMEOUT, 0);

			Navigate(
				L"about:blank",
				0);

			return;
		}

		// ATLTRACE("WAIT\n");
		SetTimer(TID_WAIT, SWAIT_TIMEOUT, 0);
	}

	/* */

	HRESULT DoCaptureImpl()
	{
		IHTMLDocument2* doc = m_WebControl.GetWebDocument();
		if (doc == 0) {
			return E_FAIL;
		}

		ATL::CComQIPtr<IViewObject> view(doc);
		if (view == 0) {
			return E_NOINTERFACE;
		}

		CLSID clsid = { 0 };
		GetEncoderClsid(L"image/png", &clsid);

		bool done = true;

		int cx = m_Item.PageWidth;
		int cy = m_Item.PageHeight;

		{
			Gdiplus::Bitmap bmp(cx, cy, PixelFormat24bppRGB);

			{
				Gdiplus::Graphics g(&bmp);
				HDC hDC = g.GetHDC();

				RECTL bounds = { 0, 0, cx, cy };

				HRESULT hRslt = view->Draw(
					DVASPECT_CONTENT,
					1,
					0,
					0,
					0,
					hDC,
					&bounds,
					0,
					0,
					0);

				g.ReleaseHDC(hDC);
			}

#ifdef _DEBUG
			{
				WCHAR path[MAX_PATH];
				lstrcpynW(path, m_Item.Path, MAX_PATH);
				PathRemoveFileSpecW(path);
				PathAppendW(path, L"WebPage-DEBUG.png");

				Gdiplus::Status st = bmp.Save(path, &clsid);
				if (st != Gdiplus::Ok) {
					done = false;
				}
			}
#endif

			{
				Gdiplus::Image* image = bmp.GetThumbnailImage(m_Item.ImageWidth, m_Item.ImageHeight);
				if (image != 0) {
					Gdiplus::Status st = image->Save(m_Item.Path, &clsid);
					if (st != Gdiplus::Ok) {
						done = false;
					}
					delete image;
				}
			}
		}

		if (!done) {
			return E_FAIL;
		}

		return S_OK;
	}

	/* */

	void DoCapture()
	{
		HRESULT hRslt = DoCaptureImpl();

		if (m_Item.Target != 0) {
			::PostMessage(
				m_Item.Target, m_Item.Message,
				(WPARAM)m_Item.Cookie,
				(LPARAM)hRslt);
		}
	}

	void DoCancelTimeout()
	{
		ATLTRACE("DoCancelTimeout()\n");

		if (IsWindowVisible()) {
			ShowWindow(SW_HIDE);
		}

		if (m_Item.Target != 0) {
			::PostMessage(
				m_Item.Target, m_Item.Message,
				(WPARAM)m_Item.Cookie,
				(LPARAM)HRESULT_FROM_WIN32(WAIT_TIMEOUT));
		}
	}

	/* */

	int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
	{
		UINT num  = 0;
		UINT size = 0;
		Gdiplus::GetImageEncodersSize(&num, &size);
		if (size == 0) return -1;

		Gdiplus::ImageCodecInfo* pImageCodecInfo = (Gdiplus::ImageCodecInfo*)malloc(size);
		Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);

		for (UINT i = 0; i < num; i++) {
			if (wcscmp(pImageCodecInfo[i].MimeType, format) == 0) {
				*pClsid = pImageCodecInfo[i].Clsid;
				free(pImageCodecInfo);
				return i;
			}
		}

		free(pImageCodecInfo);
		return -1;
	}

	/* */

	void SetBrowserSize(int cx, int cy)
	{
		WTL::CRect rcFrame;
		GetWindowRect(&rcFrame);

		ATL::CWindow& ctrl = m_WebControl.GetControl();

		WTL::CRect rcWeb;
		ctrl.GetWindowRect(&rcWeb);

		int dx = rcFrame.Width()  - rcWeb.Width();
		int dy = rcFrame.Height() - rcWeb.Height();

		SetWindowPos(
			0,
			0, 0,
			cx + dx,
			cy + dy,
			SWP_NOZORDER | SWP_NOMOVE);

		{
			HMONITOR hMon = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTOPRIMARY);

			MONITORINFOEXW mi;
			ZeroMemory(&mi, sizeof(mi));
			mi.cbSize = sizeof(MONITORINFOEXW);

			GetMonitorInfo(hMon, &mi);

			WTL::CRect rc;
			GetWindowRect(&rc);

			int x = mi.rcWork.right  - rc.Width();
			int y = mi.rcWork.bottom - rc.Height();

			SetWindowPos(
				0,
				x, y,
				0, 0,
				SWP_NOZORDER | SWP_NOSIZE);
		}
	}

	/* */

	LRESULT OnAddWebEntry(
		UINT   uMsg,
		WPARAM wParam,
		LPARAM lParam,
		BOOL&  bHandled)
	{
		WebEntry* item = (WebEntry*)lParam;
		if (item != 0) {
			m_Items.AddHead(*item);
		}

		return 0;
	}

	LRESULT OnPostWebEntry(
		UINT   uMsg,
		WPARAM wParam,
		LPARAM lParam,
		BOOL&  bHandled)
	{
		WebEntry* item = (WebEntry*)lParam;
		if (item != 0) {
			m_Items.AddHead(*item);

			delete item;
		}

		return 0;
	}

	/* */

}; // WebFrame

/* */

